package com.yexx.starter.security;

import com.yexx.starter.security.basic.*;
import com.yexx.starter.security.basic.constant.SecurityConstants;
import com.yexx.starter.security.pwd.PwdAuthenticationSecurityConfig;
import com.yexx.starter.security.sms.SmsCodeAuthenticationSecurityConfig;
import com.yexx.utils.CacheUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

/**
 * Description: security配置
 *
 * @author: zuomin (myleszelic@outlook.com)
 * @date: 2020/10/29-10:24
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    //不需要拦截的请求
    private final String[] NO_INTERCEPT_API = {"/api/public/**", "/actuator/**", "/druid/**"};

    //静态资源
    private final String[] STATIC_RESOURCE = {"/*.html", "/favicon.ico", "/**/*.html", "/**/*.js", "/**/*.css"};

    /*需要引入security starter的工程,自行实现 SecurityUserDetailsService 接口*/
    @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
    @Autowired
    private SecurityUserDetailsService securityUserService;

    @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
    @Autowired
    CacheUtil cacheUtil;

    //单点登录
    @Value("${optms.security.singleSignOn:false}")
    private Boolean singleSignOn;

    //超级验证码
    @Value("${lgt.security.superSmsCode:110120}")
    private String superSmsCode;

    /*配置 忽略拦截*/
    @Override
    public void configure(WebSecurity web) {
        //忽略 swagger文档地址, webjars, swagger资源
        web.ignoring()
                .antMatchers("/doc.html")
                .antMatchers("/webjars/**")
                .antMatchers("/swagger-resources/**")
                .antMatchers("/v2/api-docs/**");
        //忽略 不拦截的api, 静态资源
        web.ignoring()
                .antMatchers(NO_INTERCEPT_API)
                .antMatchers(STATIC_RESOURCE);
    }

    /*使用BCrypt加密密码 密码格式如：$2a$10$zSxlOyDn9W.j9Eyc5sgHIeU4nJ4g.3GlbOPNHKNE1TmPZIxl3GugK*/
    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /*自定义 userDetailsService 实现*/
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(securityUserService);
    }

    /*配置 抽象建模认证管理器 暂时不知道做什么目的*/
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    //自定义账户密码登录配置
    @Bean
    public PwdAuthenticationSecurityConfig pwdAuthenticationSecurityConfig(){
        return new PwdAuthenticationSecurityConfig(cacheUtil, securityUserService, passwordEncoder(), singleSignOn);
    }

    @Bean
    public SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfig(){
        return new SmsCodeAuthenticationSecurityConfig(cacheUtil, securityUserService, superSmsCode, singleSignOn);
    }

    /*定义http请求拦截*/
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.userDetailsService(userDetailsService());
        //关闭csrf跨站攻击
        http.csrf().disable();
        //开启跨域请求 结合corsConfigurationSource()配置使用
        http.cors();

        //开启手机号加密码登陆功能
        http.apply(pwdAuthenticationSecurityConfig());
        //开启手机号验证码登陆功能
        http.apply(smsCodeAuthenticationSecurityConfig());

        http
                //允许SecurityConstants.PATH中api
                .authorizeRequests()
                //登录 登出相关api不需要拦截
                .antMatchers(SecurityConstants.PATH.LOGIN_PHONE_NUM_PWD, SecurityConstants.PATH.LOGIN_PHONE_NUM_SMS, SecurityConstants.PATH.LOGIN_AUTH).permitAll()
                //所有的api需要拦截
                .antMatchers("/api/**").authenticated()
                //所有/admin只有admin用户角色可以访问
                .antMatchers("/admin/**").hasAnyRole("admin")
                //其他任意请求需要授权
                .anyRequest().authenticated()
                .and()
                //自定义无权限handler
                .exceptionHandling().accessDeniedHandler(new BasicAccessDeniedHandler())
                .and()
                //自定义登出过滤器
                .addFilter(new BasicLogoutFilter(new BasicLogoutSuccessHandler(), new BasicLogoutHandler(cacheUtil), SecurityConstants.PATH.LOGOUT))
                //异常拦截
                .addFilterBefore(new BasicExceptionHandleFilter(), LogoutFilter.class)
                //核心过滤器
                .addFilter(new ApiAuthenticationFilter(authenticationManager(), cacheUtil, singleSignOn));
    }

    //解决跨域问题 : spring security 推荐方式
    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        //跨域配置
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowCredentials(true);
        configuration.addAllowedOrigin("*");
        configuration.addAllowedHeader("*");
        configuration.addAllowedMethod("*");
        configuration.applyPermitDefaultValues();
        //拦截所有请求
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }

}
